/*
TODO:
	- Allow range selection
*/
/*
Inspirations:
	- http://dev.base86.com/scripts/vista-like_ajax_calendar_version_2.html
	- http://docs.jquery.com/UI/Datepicker
*/

if(!$defined(meio)) var meio = {};

meio.DatePicker = new Class({
	
	Implements: [Options, Events],
	
	options: {
		file: 'Assets/ServerSide/PHP/meio.serverside.php',
		dateFormat: 'D/M/Y',
		initSync: true,
		cache: 20,
		selectRange: true,
		sendOptions: {
			classPrefix: 'meio-datepicker-', // if you change this remember to change all your css files
			i18n: 'pt_BR',
			firstWeekDay: 0 // The first day of the week, Sun = 0, Mon = 1, ...
		},
		requestOptions: {
			method: 'get',
			link: 'cancel'
		},
		alignOptions: {
			attach: 'right', // top, left, bottom, right
	        align: 'left-top', // center, left-top, right-bottom
			offsets: {
	            x: 0,
	            y: 0
	        },
			distance: 5
		}
	},
	
	initialize: function(element, options){
		this.setOptions(options);
		var o = this.options,
			self = this;
		this.cache = new meio.Cache({capacity: this.options.cache});
		this.element = $(element) || $$(element);
		this.isInline = !(this.element.get('tag') == 'input');
		this._showing = false;
		this.createRules();
		this.createHtml();
		
		//console.log( new Date(), +new Date() );
		
		if( o.selectRange ){
			this.range = {
				date1: null,
				date2: null,
				state: true
			};
		}
		this.bound = {
			clickout: this.clickoutFunc.bind(this)
		};

		if(self.isInline){
			// inline calendar
			self.element.grab(self.container);
			self.show();
		}
		else{
			// datepicker
			var inputDate;
			// lets see whats the input value so we can sync the calendar with it
			if( this.element.get('value') != '' && o.initSync){
				inputDate = this.getDateObjFromStringDate(this.element.get('value'));
			}
			$(document.body).grab(this.container);
			self.container.placeNextTo(self.element, self.options.alignOptions);
			self.element.addEvent('focus', function(e){
				self.show();
			});
			self.element.addEvent('keydown', function(e){
				e = new Event(e);
				if(e.key == 'tab') self.hide();
			});
		}
		this.sendRequest(inputDate);
	},
	
	clickoutFunc: function(e){
		if(e.target != this.element) this.hide();
	},
	
	show: function(){
		if( !this._showing ){
			this.markDate();
			this.container.setStyle('visibility', 'visible').addEvent('clickout', this.bound.clickout);
			this._showing = true;
		}
	},
	
	hide: function(){
		if( this._showing ){
			this.container.setStyle('visibility', 'hidden').removeEvent('clickout', this.bound.clickout);
			this._showing = false;
		}
	},
	
	markDate: function(){
		/*
			This function marks the date or month or year (depending on the mode the datepicker is)
			with a class so it gets highlighted
		*/
		var self = this,
			dateObj = this.getDateObjFromStringDate(this.element.get('value')),
			cp = this.options.sendOptions.classPrefix,
			thisContent = this.content;
		
		var selectedDay = thisContent.getElement('.'+cp+'selecteddate');
		if(selectedDay) selectedDay.removeClass(cp+'selecteddate');
		
		var applySelectedClass = function(el){
			el.getParent('td').addClass(cp+'selecteddate');
		};
		
		switch(self.label.get('rel')){
		case 'month':
			if( dateObj.m.toInt() == thisContent.getElementById(cp+'current-month').get('value').toInt()
				&& dateObj.y.toInt() == thisContent.getElementById(cp+'current-year').get('value').toInt() ){
				applySelectedClass( thisContent.getElement('a:contains('+dateObj.d.toInt()+')') );
			}
			break;
		case 'year':
			if( dateObj.y.toInt() == self.label.get('html').toInt() ){
				applySelectedClass( thisContent.getElement('a[rel='+dateObj.m.toInt()+']') );
			}
			break;
		case 'decade':
			var anchors = thisContent.getElements('a'),
				firstYear = anchors[0].get('html').toInt(),
				lastYear = anchors.getLast().get('html').toInt();
			if(dateObj.y.toInt() >= firstYear && dateObj.y.toInt() <= lastYear){
				applySelectedClass( thisContent.getElement('a:contains('+dateObj.y+')') );
			}
			break;
		}
	},
	
	getDateObjFromStringDate: function(dateStr){
		var rulesArray = this.options.dateFormat.match(/[dDmMyY]/g),
			datesArray = dateStr.match(/\d+/g);
		for(var i=0; i < rulesArray.length; i++) rulesArray[i] = rulesArray[i].toLowerCase();
		return datesArray.associate(rulesArray);
	},
	
	// parses the given options.dateFormat and replaces with the correct values, based on it's dateObj param
	// just accepts jdnmyY
	// dateObj is an object with keys d(day), m(month) and y(year), representing a date
	getStringDateFromDateObj: function(dateObj){
		var parseChar = '', formattedDateArray = [], i,
			dateFormat = this.options.dateFormat;
		for(i = 0; i < dateFormat.length; i++){
			parseChar = dateFormat.charAt(i);
			formattedDateArray.push( this.rules[parseChar]?
				this.rules[parseChar](dateObj[parseChar.toLowerCase()]):
				parseChar
			);
		}
		return formattedDateArray.join('');
	},
	
	setArrowEvents: function(){
		var self = this,
			cp = this.options.sendOptions.classPrefix;
		
		var getMonthAndYear = function(prevOrNext){
			var month = self.content.getElementById(cp+'current-month').get('value').toInt(),
				year = self.content.getElementById(cp+'current-year').get('value').toInt();
			
			if(prevOrNext=='+'){
				year += parseInt(month / 12);
				month = (month % 12) + 1;
			}
			else{
				month = --month == 0? 12: month;
				year -= parseInt(month / 12);
			}
			
			return {y: year, m: month};
		};
		
		var setNewDecade = function(prevOrNext){
			var baseYear = self.label.get('html').toInt(),
				initialYear, lastYear, incDec;
				
			if(prevOrNext=='+'){
				initialYear = baseYear+9;
				lastYear = initialYear+12;
				incDec = 10;
			}
			else{
				initialYear = baseYear-11;
				lastYear = baseYear+1;
				incDec = -10;
			}
			self.label.set('html', (initialYear+1)+' - '+(lastYear-2));
			self.content.getElements('table a').each( function(el){
				el.set('html', el.get('html').toInt()+incDec);
			});
		};
		
		var clickFunc = function(nextOrPrev){
			switch(self.label.get('rel')){
			case 'month': self.sendRequest( getMonthAndYear(nextOrPrev) ); break;
			case 'year': self.label.set('html', self.label.get('html').toInt()+parseInt(nextOrPrev+'1')); break;
			case 'decade': setNewDecade(nextOrPrev);
			}
			self.markDate();
			return false;
		};
		
		this.arrowLeft.addEvent('click', clickFunc.bind(this, '-') );	
		this.arrowRight.addEvent('click', clickFunc.bind(this, '+') );
	},
	
	setLabelEvent: function(){
		var self = this,
			cp = this.options.sendOptions.classPrefix;
		
		var generateTable = function(tableHtml, type){
			return ['<table class="'+cp+type+'-table" cellpadding="0" cellspacing="2">',
				tableHtml.join(''),
			'</table>'].join('');
		};
		
		var setClickFunc = function(tableClass, labelRel, fn){
			self.content.set('html', generateTable(tableHtml, tableClass) ).getElements('a').each(function(el){
				el.addEvent('click', function(){
					self.label.set('rel', labelRel);
					fn();
					return false;
				});
			});
		};
		
		var createYearMode = function(currentYear){
			self.label.set('html', currentYear).removeClass(cp+'not-a-link');
			var tableHtml = ['<tr>'];
			self.monthAbbrs.each(function(month, i){
				tableHtml.push('<td><a rel="'+(i+1)+'" href="#">'+month+'</a></td>');
				if(i%4==3) tableHtml.push('</tr><tr>');
			});
			tableHtml.push('</tr>');
			
			setClickFunc('year', 'month', function(){
				self.sendRequest({
					y: self.label.get('html'),
					m: this.get('rel')
				});
			});
			self.markDate();
		};
		
		this.label.addEvent('click', function(){
			switch(this.get('rel')){
			case 'month':
				createYearMode( self.content.getElementById(cp+'current-year').get('value') );
				break;
			case 'year':
				this.set('rel', 'decade').addClass(cp+'not-a-link');
				var currentYear = this.get('html').toInt(),
					initialYear = (currentYear-currentYear%10)-1,
					lastYear = initialYear+12, i,
					tableHtml = ['<tr>'];
				
				self.label.set('html', (initialYear+1)+' - '+(lastYear-2));
				for(i = initialYear, y=0; i<lastYear; i++, y++){
					tableHtml.push('<td><a href="#">'+i+'</a></td>');
					if(y%4==3) tableHtml.push('</tr><tr>');
				}
				tableHtml.push('</tr>');
				setClickFunc('decade', 'year', function(){
					createYearMode(this.get('html'));
				});
				break;
			}
			self.markDate();
			return false;
		});
	},
	
	// this function creates rules for each char that can be used at the dateFormat [dDmMyY]
	// wich are then used at the parseDate
	createRules: function(){
		var self = this;
		this.rules = {
			d: function(date){
				return String(parseInt(date));
			},
			m: function(date){
				return String(parseInt(date));
			},
			y: function(date){
				return date.substring(2);
			}
		};
		for( var key in this.rules ){
			this.rules[key.toUpperCase()] = function(date){ return self.addLeadingZero(date); };
		}
	},
	
	createHtml: function(){
		/*
			Creates the html structure for the datepicker
		*/
		var cp = this.options.sendOptions.classPrefix;
		this.container = new Element('div',{
			'class': cp+'container',
			'html':
			['<div class="'+cp+'header">',
				'<a href="#" class="'+cp+'arrow-right"></a>',
				'<a href="#" class="'+cp+'arrow-left"></a>',
				'<a rel="month" href="#" class="'+cp+'label"></a>',
			'</div>',
			'<div class="'+cp+'contents">',
				'<div class="'+cp+'content"></div>',
			'</div>'].join('')
		});
		['arrow-left','arrow-right','label','content'].each(function(el){
			this[el.camelCase()] = this.container.getElement('.'+cp+el);
		}, this);
	},
	
	// adds leading zero to months and days
	addLeadingZero: function(number){
		return (parseInt(number)>=10)? String(number): '0'+number;
	},
	
	generateCacheKey: function(sendData){
		return parseInt(sendData.m)+''+parseInt(sendData.y);
	},
	
	requestSuccess: function(self, text){
		var cp = self.options.sendOptions.classPrefix,
			thisContent = self.content;
		thisContent.set('html', text).getElements('table a').each(function(el){
			el.addEvent('click', function(){
				var nDate = {
					d: this.get('html'),
					m: thisContent.getElementById(cp+'current-month').get('value'),
					y: thisContent.getElementById(cp+'current-year').get('value')
				};
				var elementValue = self.getStringDateFromDateObj(nDate);
				
				if( !self.options.selectRange ){
					self.element.set('value', elementValue);
					self.hide();
				}
				else{
					if(self.range.state){
						self.range.date1 = self.newDate(nDate);
					}
					else{
						var nDate2 = self.newDate(nDate);
						if( self.range.date1 > nDate2 ){
							console.log('primeiro maior que segundo');
							self.range.date2 = self.range.date1;
						}
						// = elementValue;
						//self.element.set('value', self.range.date1+' - '+elementValue);
						self.hide();
					}
					self.range.state = !self.range.state;
				}
				
				return false;
			});
		});
		self.label.set('html', thisContent.getElementById(cp+'label').get('value'));
		self.markDate();
	},
	
	newDate: function(day, month, year){
		var newDate = new Date();
		if($type(day)=='object'){
			month = day.m;
			year = day.y;
			day = day.d;
		}
		newDate.setDate(day);
		newDate.setMonth(month.toInt()-1);
		newDate.setFullYear(year);
		return newDate;
	},
	
	sendRequest: function(sendData){
		sendData = sendData || {};
		var self = this,
			cacheKey = this.generateCacheKey(sendData);
		
		if(this.cache.hasKey(cacheKey)){
			self.requestSuccess(self, this.cache.get(cacheKey));
		}
		else{
			if(!this.request){
				this.request = new Request($extend({
						onSuccess: function(text){
							self.requestSuccess.call(this, self, text);
						}
					},
					this.options.requestOptions
				));
				// ft = first time
				sendData.ft = 1;
				var firstSuccess = function(){
					self.setLabelEvent();
					self.setArrowEvents();
					var monthAbbrs = self.content.getElementById(self.options.sendOptions.classPrefix+'month-abbrs');
					if(monthAbbrs) self.monthAbbrs = JSON.decode(monthAbbrs.get('value'));
				};
				// executes after success and complete
				this.request.chain(firstSuccess);
			}
			var successFunc = function(text){
				self.cache.store(cacheKey, text);
				this.removeEvent('success', successFunc);
			};
			this.request.addEvent('success', successFunc);
			this.request.send({url: this.options.file, data: $extend($unlink(this.options.sendOptions), sendData) });
		}
	}
	
});
